package com.ruoyi.system.service.complex;

import com.ruoyi.system.bean.MapRouteInfo;
import com.ruoyi.system.bean.PlanTravelMethod;
import com.ruoyi.system.bean.ResultDTO;
import com.ruoyi.system.bean.rsp.PoiRsp;
import com.ruoyi.system.bean.rsp.TravelInfoPoiRsp;
import com.ruoyi.system.bean.rsp.TravelInfoRsp;
import com.ruoyi.system.domain.Poi;
import com.ruoyi.system.domain.TravelInfo;
import com.ruoyi.system.domain.TravelInfoPoi;
import com.ruoyi.system.service.ICityService;
import com.ruoyi.system.service.IPoiService;
import com.ruoyi.system.service.ITravelInfoPoiService;
import com.ruoyi.system.service.ITravelInfoService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @description TODO
 * @author FengQing
 * @date 2024-10-6 14:22
 */
@Slf4j
@Service
public class TravelPlanComplex {
    // IPoiService用于操作poi数据的接口实例
    @Resource
    private IPoiService poiService;
    // ITravelInfoService用于操作旅行信息数据的接口实例
    @Resource
    private ITravelInfoService travelInfoService;
    // ITravelInfoPoiService用于操作旅行信息和poi关联数据的接口实例
    @Resource
    private ITravelInfoPoiService travelInfoPoiService;
    @Resource
    private PoiComplexService poiComplexService;
    @Resource
    private ICityService cityService;

    /**
     * 根据行程编号和规划方法规划旅行
     *
     * @param travelNo 行程编号
     * @param method 规划方法枚举
     * @return 返回规划结果封装在ResultDTO中
     *
     * 此方法根据用户指定的行程编号和规划方法，计算并返回行程的规划结果
     * 首先，通过行程编号获取行程基本信息和关联的景点信息
     * 然后，根据不同的规划方法，调用相应的规划逻辑
     * 如果行程不存在或没有关联的景点信息，则抛出异常
     * 如果是自定义规划方法，则根据具体的规划策略（如自定义顺序、自定义时间等）进行规划
     * 如果是系统地图规划方法，则根据地图目的地或时间进行规划
     * 如果规划方法未知，则返回错误结果
     */
    public ResultDTO<TravelInfoRsp> planTravel(String travelNo, PlanTravelMethod method) {

        ResultDTO<TravelInfoRsp> resultDTO = new ResultDTO<>();
        TravelInfo travelInfo = this.travelInfoService.getByTravelNo(travelNo);

        // 检查行程是否存在
        if (travelInfo == null) {
            throw new IllegalArgumentException("行程不存在");
        }

        // 获取行程中的所有景点信息
        List<TravelInfoPoi> travelInfoPois = this.travelInfoPoiService.getTravelInfoPoiBNyTravelNo(travelNo);
        if (travelInfoPois.isEmpty()) {
            throw new IllegalArgumentException("没有具体行程无需规划");
        }

        // 提取所有景点ID
        Set<Long> poiIds = travelInfoPois.stream().map(TravelInfoPoi::getTravelPoiId).collect(Collectors.toSet());
        List<Poi> pois = this.poiService.queryByIds(poiIds);

        // 根据不同的规划方法执行对应的规划逻辑
        if (PlanTravelMethod.CUSTOM == method) {  // 用户定义-固定
            TravelInfoRsp travelInfoRsp = this.planTripByCustom(travelInfo, travelInfoPois, pois);
            resultDTO.setData(travelInfoRsp);
            return resultDTO;
        } else if (PlanTravelMethod.CUSTOM_ORDER == method) {  // 用户定义-优先级
            TravelInfoRsp travelInfoRsp = this.planTripByCustomOrder(travelInfo, travelInfoPois, pois);
            resultDTO.setData(travelInfoRsp);
            return resultDTO;
        } else if (PlanTravelMethod.CUSTOM_TIME == method) {  // 用户定义-耗时
            TravelInfoRsp travelInfoRsp = this.planTripByCustomTime(travelInfo, travelInfoPois, pois);
            resultDTO.setData(travelInfoRsp);
            return resultDTO;
        } else if (PlanTravelMethod.SYSTEM_MAP_DEST == method) {  // 系统-地图距离
            TravelInfoRsp travelInfoRsp = this.planTripByMapDest(travelInfo, travelInfoPois, pois);
            resultDTO.setData(travelInfoRsp);
            return resultDTO;
        } else if (PlanTravelMethod.SYSTEM_MAP_TIME == method) {  // 系统-地图耗时
            TravelInfoRsp travelInfoRsp = this.planTripByMapTime(travelInfo, travelInfoPois, pois);
            resultDTO.setData(travelInfoRsp);
            return resultDTO;
        } else {
            return ResultDTO.error("未知的规划方式");
        }
    }


    /**
     * 根据价值计算并分组对象（使用贪心算法）
     *
     * @param calculteValue 包含对象及其计算价值的映射
     * @param limit 单个分组的价值上限
     * @param count 单个分组的最大对象数量
     * @return 一个映射，其中包含按日计算的价值分组的对象列表
     */
    public Map<Integer, List<Object>> calculteByValue(Map<Object, Long> calculteValue, Integer limit, Integer count) {
        // 初始化用于按日分组的对象的映射
        Map<Integer, List<Object>> groupedByDay = new HashMap<>();

        // 当前日的初始化
        int currentDay = 1;

        // 当前分组的对象计数
        int currentCount = 0;

        // 当日累计价值总和
        long total = 0;

        // 将计算价值映射的条目转换为列表以便排序
        List<Map.Entry<Object, Long>> sortedEntries = new ArrayList<>(calculteValue.entrySet());

        // 按价值降序排序条目（贪心算法的关键步骤之一）
        sortedEntries.sort((e1, e2) -> Long.compare(e2.getValue(), e1.getValue()));

        // 遍历排序后的条目
        for (Map.Entry<Object, Long> entry : sortedEntries) {
            Object key = entry.getKey();
            long value = entry.getValue();

            // 检查是否需要开始新的一天分组（贪心算法的关键步骤之一）
            if (total + value > limit || currentCount >= count) {
                currentDay++;
                currentCount = 0;
                total = 0;
            }

            // 更新当日的价值总和和对象计数
            total += value;
            currentCount++;

            // 将对象添加到当前日的分组列表中
            groupedByDay.computeIfAbsent(currentDay, k -> new ArrayList<>()).add(key);
        }

        // 返回按日分组的对象映射
        return groupedByDay;
    }

    /**
     * 根据链接键计算并分组数据
     *
     * @param calculteValue 包含计算值的映射，键为对象，值为长整型
     * @param limit 单组数据的上限
     * @param count 单组数据的最大数量
     * @return 返回按天分组的数据映射，键为天数，值为对象列表
     *
     * 方法工作原理：
     * 1. 根据计算值对数据进行排序
     * 2. 根据链接键的特定模式将数据分组到不同的天数中
     * 3. 确保每组数据的总和不超过上限，并且数量不超过最大值
     * 4. 返回分组后的数据映射
     */
    public Map<Integer, List<Object>> calculteByLinkKey(Map<Object, Long> calculteValue, Integer limit, Integer count) {
        // 初始化用于按天分组的映射
        Map<Integer, List<Object>> groupedByDay = new HashMap<>();
        // 当前天数初始化为第一天
        int currentDay = 1;
        // 当前天数中的数据数量
        int currentCount = 0;
        // 当天数据的总和
        long total = 0;

        // 将计算值映射的条目转换为列表并排序，以便按值降序排列
        List<Map.Entry<Object, Long>> sortedEntries = new ArrayList<>(calculteValue.entrySet());
        sortedEntries.sort((e1, e2) -> Long.compare(e2.getValue(), e1.getValue()));

        // 用于存储上一个键的结尾部分，以便后续比较
        String lastKeyEndsWith = null;

        // 遍历排序后的条目
        for (Map.Entry<Object, Long> entry : sortedEntries) {
            Object key = entry.getKey();
            long value = entry.getValue();

            // 检查当前键是否与上一个键的结尾部分相同，如果不相同则切换到下一天
            if (lastKeyEndsWith != null && ! key.toString().startsWith(lastKeyEndsWith)) {
                currentDay++;
                currentCount = 0;
                total = 0;
            }

            // 如果添加当前值后总和超过上限或数量达到最大值，则切换到下一天
            if (total + value > limit || currentCount >= count) {
                currentDay++;
                currentCount = 0;
                total = 0;
            }

            // 更新当天的总和和数量
            total += value;
            currentCount++;

            // 将当前键添加到当前天数的列表中
            groupedByDay.computeIfAbsent(currentDay, k -> new ArrayList<>()).add(key);

            // 分解键并更新上一个键的结尾部分
            String[] parts = key.toString().split("_");
            lastKeyEndsWith = parts[1];
        }

        // 返回分组后的数据映射
        return groupedByDay;
    }


    /**
     * 根据地图时间规划旅行路线
     * 该方法通过计算不同地点之间的最短时间路线来规划旅行行程
     *
     * @param travelInfo 旅行信息，包括旅行的起点、终点、时间等基本信息
     * @param travelInfoPois 旅行中计划访问的地点信息
     * @param pois 可用的地点信息库，用于查找详细的地点数据
     * @return 返回一个旅行信息响应对象，包含规划后的旅行路线和相关详情
     */

    private TravelInfoRsp planTripByMapTime(TravelInfo travelInfo, List<TravelInfoPoi> travelInfoPois, List<Poi> pois) {
        // 将地点信息转换为Map，便于后续快速查询
        Map<Long, Poi> poiMap = pois.stream().collect(Collectors.toMap(Poi::getId, Function.identity()));

        // 将旅行中计划访问的地点信息转换为Map，便于后续快速查询
        Map<Long, TravelInfoPoi> infoPoiMap = travelInfoPois.stream().collect(Collectors.toMap(TravelInfoPoi::getTravelPoiId, Function.identity()));

        // 用于存储地点之间的路径信息
        Map<String, String[]> mapDestRutes = new HashMap<>();

        // 用于存储路线的时间信息
        Map<Object, Long> mapValueRutes = new HashMap<>();

        // 计算每两个地点之间的路径信息
        for (int i = 0; i < travelInfoPois.size() - 1; i++) {
            TravelInfoPoi firstElement = travelInfoPois.get(i);
            for (int j = i + 1; j < travelInfoPois.size(); j++) {
                TravelInfoPoi secondElement = travelInfoPois.get(j);
                mapDestRutes.put(firstElement.getTravelPoiId() + "_" + secondElement.getTravelPoiId(),
                        new String[]{firstElement.getLat() + "," + firstElement.getLng(), secondElement.getLat() + "," + secondElement.getLng()});
            }
        }

        // 并行计算每两个地点之间的最短时间路线
        Map<String, MapRouteInfo> mapData = new ConcurrentHashMap<>();
        mapDestRutes.keySet().parallelStream().forEach((k) -> {
            String[] strings = mapDestRutes.get(k);
            MapRouteInfo mapRouteInfo;
            try {
                mapRouteInfo = this.poiComplexService.calculateMapRoute(strings[0], strings[1]);
            } catch (Exception e) {
                throw new RuntimeException("调用地图API失败：" + e.getMessage());
            }
            mapData.put(k, mapRouteInfo);
        });

        // 将计算出的路线时间信息存储到Map中
        mapData.forEach((key, value) -> mapValueRutes.put(key, (long) value.getTravelDuration()));

        // 根据路线时间信息计算旅行行程
        Map<Integer, List<Object>> calculteAutoData = this.calculteByLinkKey(mapValueRutes, 360, 3);

        // 创建旅行信息响应对象，并设置基本旅行信息
        TravelInfoRsp travelInfoRsp = new TravelInfoRsp();
        travelInfoRsp.setId(travelInfo.getId());
        travelInfoRsp.setUserId(travelInfo.getUserId());
        travelInfoRsp.setTravelNo(travelInfo.getTravelNo());
        travelInfoRsp.setTravelName(travelInfo.getTravelName());
        travelInfoRsp.setTravelStartTime(travelInfo.getTravelStartTime());
        travelInfoRsp.setTravelEndTime(travelInfo.getTravelEndTime());
        travelInfoRsp.setTotalDay(travelInfo.getTotalDay());
        travelInfoRsp.setStartCityId(travelInfo.getStartCityId());
        travelInfoRsp.setStartCityName(travelInfo.getStartCityName());
        travelInfoRsp.setEndCityId(travelInfo.getEndCityId());
        travelInfoRsp.setEndCityName(travelInfo.getEndCityName());
        travelInfoRsp.setAuditStatus(travelInfo.getAuditStatus());
        travelInfoRsp.setStatus(travelInfo.getStatus());

        // 设置详细的地点和路线信息
        calculteAutoData.forEach((day, poiIds) -> {
            List<String> poiNames = new LinkedList<>();

            List<TravelInfoPoiRsp> poiRspList = poiIds.stream().map(p -> {
                String[] s = p.toString().split("_");
                String key = s[0];
                if (CollectionUtils.isNotEmpty(travelInfoRsp.getTravelInfoPoiList())) {
                    key = s[1];
                }

                TravelInfoPoi poit = infoPoiMap.get(Long.parseLong(key));
                TravelInfoPoiRsp travelInfoPoiRsp = new TravelInfoPoiRsp();
                travelInfoPoiRsp.setId(poit.getId());
                travelInfoPoiRsp.setTravelNo(poit.getTravelNo());
                travelInfoPoiRsp.setTravelRemark(poit.getTravelRemark());
                travelInfoPoiRsp.setTravelPoiId(poit.getTravelPoiId());
                travelInfoPoiRsp.setTravelStartTime(poit.getTravelStartTime());
                travelInfoPoiRsp.setTravelEndTime(poit.getTravelEndTime());
                travelInfoPoiRsp.setTravelOrder(poit.getTravelOrder());
                travelInfoPoiRsp.setDay(day);

                // 通过景点ID从地图中获取景点详细信息，并设置到响应对象中
                Poi poi = poiMap.get(poit.getTravelPoiId());
                PoiRsp poiRsp = new PoiRsp();
                poiRsp.setId(poi.getId());
                poiRsp.setPoiId(poi.getPoiId());
                poiRsp.setName(poi.getName());
                poiRsp.setType(poi.getType());
                poiRsp.setCityId(poi.getCityId());
                poiRsp.setCityName(poi.getCityName());
                poiRsp.setAddressCn(poi.getAddressCn());
                poiRsp.setAddressLocal(poi.getAddressLocal());
                poiRsp.setLng(poi.getLng());
                poiRsp.setLat(poi.getLat());
                poiRsp.setPoiSource(poi.getPoiSource());
                poiRsp.setPoiSourceId(poi.getPoiSourceId());
                poiRsp.setPrefectFlag(poi.getPrefectFlag());
                poiRsp.setLevel(poi.getLevel());
                poiRsp.setNameAlias(poi.getNameAlias());
                poiRsp.setDescription(poi.getDescription());
                poiRsp.setStatus(poi.getStatus());

                travelInfoPoiRsp.setPoiRsp(poiRsp);
                poiNames.add(poiRsp.getName());
                return travelInfoPoiRsp;
            }).collect(Collectors.toList());

            travelInfoRsp.getPoiNames().addAll(poiNames);
            travelInfoRsp.getTravelInfoPoiList().addAll(poiRspList);
        });

        return travelInfoRsp;
    }

    /**
     * 根据目的地规划旅行路线
     * 该方法通过地图服务计算不同景点之间的最短路线，并根据计算结果生成旅行信息响应
     *
     * @param travelInfo 旅行信息，包含旅行的基本详情
     * @param travelInfoPois 旅行信息POI列表，包含旅行中计划访问的具体地点信息
     * @param pois POI列表，包含所有可能的旅游景点信息
     * @return 返回一个TravelInfoRsp对象，包含更新后的旅行信息和路线规划详情
     */
    private TravelInfoRsp planTripByMapDest(TravelInfo travelInfo, List<TravelInfoPoi> travelInfoPois, List<Poi> pois) {
        // 将POI列表转换为Map，以便快速查找
        Map<Long, Poi> poiMap = pois.stream().collect(Collectors.toMap(Poi::getId, poi -> poi));
        // 将旅行信息POI列表转换为Map，以便快速查找
        Map<Long, TravelInfoPoi> infoPoiMap = travelInfoPois.stream().collect(Collectors.toMap(TravelInfoPoi::getTravelPoiId, Function.identity()));

        // 用于存储目的地之间的路线信息
        Map<String, String[]> mapDestRutes = new HashMap<>();
        // 用于存储路线的价值，如距离
        Map<Object, Long> mapValueRutes = new HashMap<>();

        // 遍历旅行信息POI列表，计算每对景点之间的路线
        for (int i = 0; i < travelInfoPois.size() - 1; i++) {
            TravelInfoPoi firstElement = travelInfoPois.get(i);
            for (int j = i + 1; j < travelInfoPois.size(); j++) {
                TravelInfoPoi secondElement = travelInfoPois.get(j);
                // 将两个景点的ID组合成键，其坐标组合成值，存储到mapDestRutes中
                mapDestRutes.put(firstElement.getTravelPoiId() + "_" + secondElement.getTravelPoiId(),
                        new String[]{firstElement.getLat() + "," + firstElement.getLng(), secondElement.getLat() + "," + secondElement.getLng()});
            }
        }

        // 并行计算每对景点之间的实际路线信息
        Map<String, MapRouteInfo> mapData = new ConcurrentHashMap<>();
        mapDestRutes.keySet().parallelStream().forEach((k) -> {
            String[] strings = mapDestRutes.get(k);
            MapRouteInfo mapRouteInfo;
            try {
                // 调用地图服务计算路线
                mapRouteInfo = this.poiComplexService.calculateMapRoute(strings[0], strings[1]);
            } catch (Exception e) {
                throw new RuntimeException("调用地图API失败：" + e.getMessage());
            }
            // 将计算得到的路线信息存储到mapData中
            mapData.put(k, mapRouteInfo);
        });

        // 根据计算得到的路线信息，计算每条路线的总距离，并存储到mapValueRutes中
        mapData.forEach((key, value) -> mapValueRutes.put(key, (long) value.getTravelDistance()));

        // 根据路线价值计算每天的旅行计划
        Map<Integer, List<Object>> calculteAutoData = this.calculteByLinkKey(mapValueRutes, 300, 3);

        // 创建并初始化旅行信息响应对象
        TravelInfoRsp travelInfoRsp = new TravelInfoRsp();
        travelInfoRsp.setId(travelInfo.getId());
        travelInfoRsp.setUserId(travelInfo.getUserId());
        travelInfoRsp.setTravelNo(travelInfo.getTravelNo());
        travelInfoRsp.setTravelName(travelInfo.getTravelName());
        travelInfoRsp.setTravelStartTime(travelInfo.getTravelStartTime());
        travelInfoRsp.setTravelEndTime(travelInfo.getTravelEndTime());
        travelInfoRsp.setTotalDay(travelInfo.getTotalDay());
        travelInfoRsp.setStartCityId(travelInfo.getStartCityId());
        travelInfoRsp.setStartCityName(travelInfo.getStartCityName());
        travelInfoRsp.setEndCityId(travelInfo.getEndCityId());
        travelInfoRsp.setEndCityName(travelInfo.getEndCityName());
        travelInfoRsp.setAuditStatus(travelInfo.getAuditStatus());
        travelInfoRsp.setStatus(travelInfo.getStatus());

        // 遍历每天的旅行计划，生成详细的旅行信息POI响应对象
        calculteAutoData.forEach((day, poiIds) -> {
            List<String> poiNames = new LinkedList<>();

            List<TravelInfoPoiRsp> poiRspList = poiIds.stream().map(p -> {
                String[] s = p.toString().split("_");
                String key = s[0];
                if (CollectionUtils.isNotEmpty(travelInfoRsp.getTravelInfoPoiList())) {
                    key = s[1];
                }

                TravelInfoPoi poit = infoPoiMap.get(Long.parseLong(key));
                TravelInfoPoiRsp travelInfoPoiRsp = new TravelInfoPoiRsp();
                travelInfoPoiRsp.setId(poit.getId());
                travelInfoPoiRsp.setTravelNo(poit.getTravelNo());
                travelInfoPoiRsp.setTravelRemark(poit.getTravelRemark());
                travelInfoPoiRsp.setTravelPoiId(poit.getTravelPoiId());
                travelInfoPoiRsp.setTravelStartTime(poit.getTravelStartTime());
                travelInfoPoiRsp.setTravelEndTime(poit.getTravelEndTime());
                travelInfoPoiRsp.setTravelOrder(poit.getTravelOrder());
                travelInfoPoiRsp.setDay(day);

                // 通过景点ID从地图中获取景点详细信息，并设置到响应对象中
                Poi poi = poiMap.get(poit.getTravelPoiId());
                PoiRsp poiRsp = new PoiRsp();
                poiRsp.setId(poi.getId());
                poiRsp.setPoiId(poi.getPoiId());
                poiRsp.setName(poi.getName());
                poiRsp.setType(poi.getType());
                poiRsp.setCityId(poi.getCityId());
                poiRsp.setCityName(poi.getCityName());
                poiRsp.setAddressCn(poi.getAddressCn());
                poiRsp.setAddressLocal(poi.getAddressLocal());
                poiRsp.setLng(poi.getLng());
                poiRsp.setLat(poi.getLat());
                poiRsp.setPoiSource(poi.getPoiSource());
                poiRsp.setPoiSourceId(poi.getPoiSourceId());
                poiRsp.setPrefectFlag(poi.getPrefectFlag());
                poiRsp.setLevel(poi.getLevel());
                poiRsp.setNameAlias(poi.getNameAlias());
                poiRsp.setDescription(poi.getDescription());
                poiRsp.setStatus(poi.getStatus());

                travelInfoPoiRsp.setPoiRsp(poiRsp);
                poiNames.add(poiRsp.getName());
                return travelInfoPoiRsp;
            }).collect(Collectors.toList());

            travelInfoRsp.getPoiNames().addAll(poiNames);
            travelInfoRsp.getTravelInfoPoiList().addAll(poiRspList);
        });

        return travelInfoRsp;
    }

    /**
     * 根据自定义时间规划旅行行程
     * 该方法的主要作用是根据用户提供的旅行信息和景点列表，计算出一个优化后的旅行计划
     * 它会考虑每个景点的停留时间，并尝试在每天内均衡分配这些景点
     *
     * @param travelInfo 用户提供的旅行基本信息，包括旅行的起止时间、总天数等
     * @param travelInfoPois 旅行中计划参观的景点列表，包含了每个景点的旅行相关信息
     * @param pois 景点信息列表，提供了所有可用景点的详细信息
     * @return 返回一个TravelInfoRsp对象，包含了优化后的旅行计划信息
     */
    private TravelInfoRsp planTripByCustomTime(TravelInfo travelInfo, List<TravelInfoPoi> travelInfoPois, List<Poi> pois) {

        // 将景点列表转换为Map，以便快速查找
        Map<Long, Poi> poiMap = pois.stream().collect(Collectors.toMap(Poi::getId, poi -> poi));
        // 将旅行中计划参观的景点列表转换为Map，以便快速查找
        Map<Long, TravelInfoPoi> infoPoiMap = travelInfoPois.stream().collect(Collectors.toMap(TravelInfoPoi::getTravelPoiId, Function.identity()));

        // 用于存储每个景点的推荐停留时间
        Map<Object, Long> mapTimeRutes = new HashMap<>();

        // 计算每个景点的推荐停留时间
        for (int i = 0; i < travelInfoPois.size() - 1; i++) {
            TravelInfoPoi infoPoi = travelInfoPois.get(i);
            long time = (infoPoi.getTravelStartTime().getTime() - infoPoi.getTravelEndTime().getTime()) / (1000 * 60);
            mapTimeRutes.put(infoPoi.getTravelPoiId(), time);
        }

        // 根据推荐停留时间计算每天推荐的景点列表
        Map<Integer, List<Object>> dayPoi = this.calculteByValue(mapTimeRutes, 360, 3);

        // 创建并初始化返回对象
        TravelInfoRsp travelInfoRsp = new TravelInfoRsp();
        travelInfoRsp.setId(travelInfo.getId());
        travelInfoRsp.setUserId(travelInfo.getUserId());
        travelInfoRsp.setTravelNo(travelInfo.getTravelNo());
        travelInfoRsp.setTravelName(travelInfo.getTravelName());
        travelInfoRsp.setTravelStartTime(travelInfo.getTravelStartTime());
        travelInfoRsp.setTravelEndTime(travelInfo.getTravelEndTime());
        travelInfoRsp.setTotalDay(travelInfo.getTotalDay());
        travelInfoRsp.setStartCityId(travelInfo.getStartCityId());
        travelInfoRsp.setStartCityName(travelInfo.getStartCityName());
        travelInfoRsp.setEndCityId(travelInfo.getEndCityId());
        travelInfoRsp.setEndCityName(travelInfo.getEndCityName());
        travelInfoRsp.setAuditStatus(travelInfo.getAuditStatus());
        travelInfoRsp.setStatus(travelInfo.getStatus());

        // 根据每天推荐的景点列表，生成详细的景点访问计划
        dayPoi.forEach((key, poiIds) -> {
            List<String> poiNames = new LinkedList<>();

            List<TravelInfoPoiRsp> poiRspList = poiIds.stream().map(p -> {
                TravelInfoPoi poit = infoPoiMap.get(p);
                TravelInfoPoiRsp travelInfoPoiRsp = new TravelInfoPoiRsp();
                travelInfoPoiRsp.setId(poit.getId());
                travelInfoPoiRsp.setTravelNo(poit.getTravelNo());
                travelInfoPoiRsp.setTravelRemark(poit.getTravelRemark());
                travelInfoPoiRsp.setTravelPoiId(poit.getTravelPoiId());
                travelInfoPoiRsp.setTravelStartTime(poit.getTravelStartTime());
                travelInfoPoiRsp.setTravelEndTime(poit.getTravelEndTime());
                travelInfoPoiRsp.setTravelOrder(poit.getTravelOrder());
                travelInfoPoiRsp.setDay(key);

                // 通过景点ID从地图中获取景点详细信息，并设置到响应对象中
                Poi poi = poiMap.get(poit.getTravelPoiId());
                PoiRsp poiRsp = new PoiRsp();
                poiRsp.setId(poi.getId());
                poiRsp.setPoiId(poi.getPoiId());
                poiRsp.setName(poi.getName());
                poiRsp.setType(poi.getType());
                poiRsp.setCityId(poi.getCityId());
                poiRsp.setCityName(poi.getCityName());
                poiRsp.setAddressCn(poi.getAddressCn());
                poiRsp.setAddressLocal(poi.getAddressLocal());
                poiRsp.setLng(poi.getLng());
                poiRsp.setLat(poi.getLat());
                poiRsp.setPoiSource(poi.getPoiSource());
                poiRsp.setPoiSourceId(poi.getPoiSourceId());
                poiRsp.setPrefectFlag(poi.getPrefectFlag());
                poiRsp.setLevel(poi.getLevel());
                poiRsp.setNameAlias(poi.getNameAlias());
                poiRsp.setDescription(poi.getDescription());
                poiRsp.setStatus(poi.getStatus());

                travelInfoPoiRsp.setPoiRsp(poiRsp);
                poiNames.add(poiRsp.getName());
                return travelInfoPoiRsp;
            }).collect(Collectors.toList());

            // 将处理后的景点信息列表和景点名称列表设置到旅行信息响应对象中
            travelInfoRsp.getPoiNames().addAll(poiNames);
            travelInfoRsp.getTravelInfoPoiList().addAll(poiRspList);
        });

        // 返回旅行信息响应对象
        return travelInfoRsp;
    }


    public static void main(String[] args) {
        TravelPlanComplex planner = new TravelPlanComplex();

        Map<Object, Long> mapTimeRutes = new HashMap<>();

        mapTimeRutes.put(1L, 90L); // 景点1，耗时90分钟
        mapTimeRutes.put(2L, 120L); // 景点2，耗时120分钟
        mapTimeRutes.put(3L, 150L); // 景点3，耗时150分钟
        mapTimeRutes.put(4L, 60L); // 景点4，耗时60分钟
        mapTimeRutes.put(5L, 180L); // 景点5，耗时180分钟

        Map<Integer, List<Object>> listMap = planner.calculteByValue(mapTimeRutes, 360, 3);
        System.out.println(listMap);
    }

    /**
     * 根据用户自定义计划生成旅行信息响应对象
     * 该方法的主要作用是将用户提交的旅行计划信息及相关的景点信息转换为系统响应对象
     * 它包括基本的旅行信息以及计划中每个景点的详细信息
     *
     * @param travelInfo 旅行基本信息对象，包含旅行的编号、名称、时间等信息
     * @param travelInfoPois 旅行计划中包含的景点信息列表，每个对象代表计划中一个景点的信息
     * @param pois 景点信息列表，系统中所有可用景点的信息库，用于补充travelInfoPois中景点的详细信息
     * @return 返回一个TravelInfoRsp对象，包含了旅行的所有信息以及每个景点的详细信息
     */
    private TravelInfoRsp planTripByCustom(TravelInfo travelInfo, List<TravelInfoPoi> travelInfoPois, List<Poi> pois) {

        // 将景点列表转换为Map，以便通过景点ID快速查找景点信息
        Map<Long, Poi> poiMap = pois.stream().collect(Collectors.toMap(Poi::getId, poi -> poi));

        // 创建旅行信息响应对象，并设置基本的旅行信息
        TravelInfoRsp travelInfoRsp = new TravelInfoRsp();
        travelInfoRsp.setId(travelInfo.getId());
        travelInfoRsp.setUserId(travelInfo.getUserId());
        travelInfoRsp.setTravelNo(travelInfo.getTravelNo());
        travelInfoRsp.setTravelName(travelInfo.getTravelName());
        travelInfoRsp.setTravelStartTime(travelInfo.getTravelStartTime());
        travelInfoRsp.setTravelEndTime(travelInfo.getTravelEndTime());
        travelInfoRsp.setTotalDay(travelInfo.getTotalDay());
        travelInfoRsp.setStartCityId(travelInfo.getStartCityId());
        travelInfoRsp.setStartCityName(travelInfo.getStartCityName());
        travelInfoRsp.setEndCityId(travelInfo.getEndCityId());
        travelInfoRsp.setEndCityName(travelInfo.getEndCityName());
        travelInfoRsp.setAuditStatus(travelInfo.getAuditStatus());
        travelInfoRsp.setStatus(travelInfo.getStatus());

        // 处理旅行计划中的每个景点，将其转换为响应对象，并收集每个景点的名称
        List<String> poiNames = new ArrayList<>(travelInfoPois.size());
        List<TravelInfoPoiRsp> infoPoiRsps = travelInfoPois.stream().map(poit -> {
            TravelInfoPoiRsp travelInfoPoiRsp = new TravelInfoPoiRsp();
            travelInfoPoiRsp.setId(poit.getId());
            travelInfoPoiRsp.setTravelNo(poit.getTravelNo());
            travelInfoPoiRsp.setTravelRemark(poit.getTravelRemark());
            travelInfoPoiRsp.setTravelPoiId(poit.getTravelPoiId());
            travelInfoPoiRsp.setTravelStartTime(poit.getTravelStartTime());
            travelInfoPoiRsp.setTravelEndTime(poit.getTravelEndTime());
            travelInfoPoiRsp.setTravelOrder(poit.getTravelOrder());

            // 通过景点ID从地图中获取景点详细信息，并设置到响应对象中
            Poi poi = poiMap.get(poit.getTravelPoiId());
            PoiRsp poiRsp = new PoiRsp();
            poiRsp.setId(poi.getId());
            poiRsp.setPoiId(poi.getPoiId());
            poiRsp.setName(poi.getName());
            poiRsp.setType(poi.getType());
            poiRsp.setCityId(poi.getCityId());
            poiRsp.setCityName(poi.getCityName());
            poiRsp.setAddressCn(poi.getAddressCn());
            poiRsp.setAddressLocal(poi.getAddressLocal());
            poiRsp.setLng(poi.getLng());
            poiRsp.setLat(poi.getLat());
            poiRsp.setPoiSource(poi.getPoiSource());
            poiRsp.setPoiSourceId(poi.getPoiSourceId());
            poiRsp.setPrefectFlag(poi.getPrefectFlag());
            poiRsp.setLevel(poi.getLevel());
            poiRsp.setNameAlias(poi.getNameAlias());
            poiRsp.setDescription(poi.getDescription());
            poiRsp.setStatus(poi.getStatus());
            travelInfoPoiRsp.setPoiRsp(poiRsp);
            poiNames.add(poiRsp.getName());
            return travelInfoPoiRsp;
        }).collect(Collectors.toList());

        // 将处理后的景点信息列表和景点名称列表设置到旅行信息响应对象中
        travelInfoRsp.setTravelInfoPoiList(infoPoiRsps);
        travelInfoRsp.setPoiNames(poiNames);

        // 返回旅行信息响应对象
        return travelInfoRsp;
    }


    /**
     * 根据自定义顺序规划旅行路线
     * 此方法首先根据旅行顺序对旅行信息点进行排序，然后调用另一个方法来根据这些排序后的点规划旅行路线
     *
     * @param travelInfo 旅行信息，包含旅行的基本信息
     * @param travelInfoPois 旅行信息点列表，每个点代表旅行中的一个具体地点，包含顺序信息
     * @param pois 所有可能的景点列表，用于规划路线
     * @return 返回一个旅行信息响应对象，包含规划后的旅行路线信息
     */
    private TravelInfoRsp planTripByCustomOrder(TravelInfo travelInfo, List<TravelInfoPoi> travelInfoPois, List<Poi> pois) {
        // 根据旅行顺序对旅行信息点进行排序，确保后续处理时顺序正确
        travelInfoPois.sort((o1, o2) -> Math.toIntExact(o1.getTravelOrder() - o2.getTravelOrder()));

        // 调用另一个方法，根据排序后的旅行信息点来规划旅行路线
        return this.planTripByCustom(travelInfo, travelInfoPois, pois);
    }

}
